package superviseur.bornes;

import java.io.*;
import java.net.*;
import java.util.*;
import net.hangar5.xmlrpc.*;
import superviseur.ParkingCenter;
import superviseur.database.TBornes;

/**
 *
 * <p>Titre : Bornes </p>
 * <p>Description :<br>
 *<br>
 *xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
 *<br>
 *                 COMMUNICATION AVEC LES BORNES<br>
 *<br>
 *    Centre nodal de communication avec les bornes, singleton<br>
 *<br>
 *  Envoi de requêtes XML-RPC aux bornes:<br>
 *     Le superviseur communique avec une seule borne:<br>
 *     - initialiseBorne<br>
 *     - messageGardien<br>
 *     - codeCAP<br>
 *     - autoriseEntree<br>
 * <br>
 *     Le superviseur communique avec toutes les bornes enregistrées:<br>
 *     - eclairageAllBornes<br>
 *     - processAlarme<br>
 *     - setParkingPlein<br>
 * <br>
 *  Reception des requetes en provenance des bornes:<br>
 *    - enregistrementAcces<br>
 *    - AppelGardien<br>
 *    - Defaut<br>
 *    - Demande autorisation passage<br>
 *    - Evenement Vehicule (entree et sortie)<br>
 *<br>
 *xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
 * <br>
 *            SECURITE DES LIAISONS AVEC LES BORNES<br>
 * <br>
 * La sécurité impose une surveillance constante des liaisons avec les bornes:<br>
 * Dans le cas d'une alarme incendie, le superviseur positionne
 * toutes les bornes en mode Alarme (barrières ouvertes)<br>
 * Le superviseur teste chaque borne toutes les 15 secondes environ
 *  si la borne ne répond pas, elle en informe le gardien(sonore et visuelle).<br>
 * Le superviseur garde en mémoire les bornes non accessibles.<br>
 * Si la liaison est rétablie, le gardien en est informé.<br>
 * Chaque borne attend une requête de test de liaison.<br>
 * si aucune requête n'est reçue pendant une période de 30s,
 * la borne se place en mode Alarme (barrière ouverte).
 * la borne retourne en mode Normal à la réception d'une requête de test.<br>
 * <br>
 *xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>
 * </p>
 * <p>Copyright : Copyright (c) 2003</p>
 * @author Gilles Petot
 * @version 1.0
 **/



public class Bornes
{
    private static Bornes bornes;

    private static final int minWorkers = 6;  // these are the TINI values, for full scale JVMs the value should be larger
    private static final int maxWorkers = 12;  // these are the TINI values, for full scale JVMs the value should be larger
    private static final int maxKeepAlives = 12;  // these are the TINI values, for full scale JVMs the value should be larger
    private static final int workerIdleLife = 120000; // two minutes
    private static final int socketReadTimeout = 120000; // two minutes

    /**
     * Memorisation des bornes en défaut
     */
    private Vector defautBornes = new Vector();

    /**
     * Un serveur XML-RPC traite les requêtes des bornes
     */
    private  RpcServer server;

    /**
     * Port du serveur XML-RPC
     */
    private int port = 6080;

    /**
     * Thread du serveur XML-RPC (listen)
     */
    private  Thread threadServer;

    /**
     * Un Client XML-RPC exécute des requêtes sur les bornes enregistrées
     */
    private RpcClient client;

    /**
     * Le client transmet la requête à la borne localisée par son URL
     */
    private String myUrl;

    /**
     *  Les bornes enregistrées
     */
    private TBornes tBornes;


    private ExecMethodes execMethodes;

    private ClockTest clockTest;



    private Bornes()
    {}

    public static Bornes getInstance()
    {
	if (bornes == null)
	{
	    bornes = new Bornes();
	}
	return bornes;
    }


    private void init()
    {
	try
	{
	    // creation du server
	    ServerSocket ss = new ServerSocket(port);
	    server = new RpcServer( ss , minWorkers, maxWorkers, maxKeepAlives, workerIdleLife, socketReadTimeout);
	    Dispatcher dispatcher = server.getDispatcher();
	    dispatcher.setVerbose(false);
	    addMethods(dispatcher);
	    threadServer = new Thread(server);
	    threadServer.setDaemon(false);
	    threadServer.setName("threadServerBornes");
	    threadServer.start();
	}
	catch (Exception e)
	{
	    System.out.println("creation server bornes impossible: " + e.getMessage());
	}

	// creation du client
	client = new RpcClient();
	client.setKeepAlive(false);
	execMethodes = new ExecMethodes();
    }

    public void init(TBornes tBornes, int portServerBornes)
    {
	this.tBornes = tBornes;
	this.port = portServerBornes;
	init();
	startTestLiaisonBornes();
    }

    private void shutDownServer()
    {
	try
	{
	    server.shutDown();
	}
	catch (IOException io)
	{
	    System.out.println("erreur shutdown server: " + io.toString());
	}
	//server = null;
    }

    public void destroy()
    {
	shutDownServer();
	stopTestLiaisonBornes();
	bornes = null;
    }

    // utilise par ManageBornesServlet, CapServlet, BorneAppletServlet
    public TBornes gettBornes()
    {
	return tBornes;
    }

    public int getNumberBornes()
    {
	return tBornes.getNombreBornes();
    }

    private Vector getVDefaut(int numeroBorne)
    {
	Vector result = null;
	if (!defautBornes.isEmpty())
	{
	    int l = defautBornes.size();
	    Vector vDefaut = null;
	    for (int j=0; j<l; j++)
	    {
		vDefaut = (Vector)defautBornes.elementAt(j);
		int num = ((Integer)vDefaut.elementAt(1)).intValue();
		System.out.println("getVdefaut: num= " + num + "   numeroBorne= " + numeroBorne);
		if ( num == numeroBorne)
		{
		    result = vDefaut;
		    break;
		}
	    }
	}
	return result;
    }

    //***************************************************************************
    //         SERVEUR
    //***************************************************************************

    public String getUrlServer()
    {
	String myUrl = null;
	String adresseIP = "";
	String strPort = ":" + server.getPortNumber();
	try
	{
	    adresseIP = java.net.InetAddress.getLocalHost().getHostAddress();
	    myUrl = "http://" + adresseIP + strPort;
	}
	catch (java.net.UnknownHostException uhe) {System.out.println(uhe.toString());}
	return myUrl;
    }

    public int getPortServer()
    {
	return  server.getPortNumber();
    }

    public void addMethods( Dispatcher dispatcher )
    {
	IMethod m;

	m = new EnregistrementAcces();
	dispatcher.addMethod( m);
	m = new AppelGardien();
	dispatcher.addMethod( m);
	m = new Defaut();
	dispatcher.addMethod( m);
	m = new GetAutorise();
	dispatcher.addMethod( m);
	m = new EvVehicule();
	dispatcher.addMethod( m);
	return;
    }


    //******************** une classe par methode *********************************

    public class EnregistrementAcces extends Method
    {
	private static final String strName = "enregistrementAcces";
	public String getName()
	{
	    return strName;
	}

	public Object execute(Vector vParams, String IPClient) throws RpcException
	{
	    Object oRet;
	    try
	    {
		String nomPoste = (String)vParams.elementAt(0);
		int numBorne = ((Integer)vParams.elementAt(1)).intValue();
		String url = (String)vParams.elementAt(2);
		boolean init = ((Boolean)vParams.elementAt(3)).booleanValue();
		//System.out.println("client: " + IPClient);
		oRet = new Integer(ParkingCenter.getInstance().enregistrementAcces(nomPoste, numBorne, url, init));
	    }
	    catch( ClassCastException x )
	    {
		oRet = null;
		throw new RpcException(RpcException.BADPARAMTYPE );
	    }
	    return oRet;
	}
    }

    public class AppelGardien extends Method
    {
	private static final String strName = "appelGardien";
	public String getName()
	{
	    return strName;
	}

	public Object execute(Vector vParams, String IPClient) throws RpcException
	{
	    Object oRet;
	    try
	    {
		int numBorne = ((Integer)vParams.elementAt(0)).intValue();
		oRet = new Boolean(ParkingCenter.getInstance().appelGardien(numBorne));
	    }
	    catch( ClassCastException x )
	    {
		oRet = null;
		throw new RpcException( RpcException.BADPARAMTYPE );
	    }
	    return oRet;
	}
    }

    public class Defaut extends Method
    {
	private static final String strName = "defaut";
	public String getName()
	{
	    return strName;
	}

	public Object execute(Vector vParams, String IPClient) throws RpcException
	{
	    Object oRet;
	    try
	    {
		int numBorne = ((Integer)vParams.elementAt(0)).intValue();
		String codeDefaut =  (String)vParams.elementAt(1);
		oRet = new Boolean(ParkingCenter.getInstance().defautBorne(numBorne, codeDefaut));
	    }
	    catch( ClassCastException x )
	    {
		oRet = null;
		throw new RpcException( RpcException.BADPARAMTYPE );
	    }
	    return oRet;
	}
    }


    public class GetAutorise extends Method
    {
	private static final String strName = "isAutorise";
	public String getName()
	{
	    return strName;
	}

	public Object execute(Vector vParams, String IPClient) throws RpcException
	{
	    Object oRet;
	    try
	    {
		int numBorne = ((Integer)vParams.elementAt(0)).intValue();
		boolean carte = ((Boolean)vParams.elementAt(1)).booleanValue();
		String code = (String)vParams.elementAt(2);
		oRet = new Boolean (ParkingCenter.getInstance().isAutorise(numBorne, carte, code));
		//System.out.println("numBorne: " + numBorne + "    isAutorise: " + ((Boolean)oRet).booleanValue() + "   code: " + code);
	    }
	    catch( ClassCastException x )
	    {
		oRet = null;
		throw new RpcException( RpcException.BADPARAMTYPE );
	    }
	    return oRet;
	}

    }

    public class EvVehicule extends Method
    {
	private static final String strName = "evVehicule";
	public String getName()
	{
	    return strName;
	}

	public Object execute(Vector vParams, String IPClient) throws RpcException
	{
	    Object oRet;
	    try
	    {
		int numBorne = ((Integer)vParams.elementAt(0)).intValue();
		int codeEvenementVehicule = ((Integer)vParams.elementAt(1)).intValue();
		ParkingCenter.getInstance().evVehicule(numBorne, codeEvenementVehicule);
		oRet = new Boolean(true);
	    }
	    catch( ClassCastException x )
	    {
		oRet = null;
		throw new RpcException( RpcException.BADPARAMTYPE );
	    }
	    return oRet;
	}

    }


    //***************************************************************************
    //         CLIENT
    //***************************************************************************


    private void setURL(String url)
    {
	myUrl = url;
	try
	{
	    client.setUrl(url);
	}
	catch (Exception other)
	{
	    myUrl = null;
	    System.err.println ("Exception in client: "+other);
	    other.printStackTrace();
	}
	//client.setKeepAlive( true );
    }

    private String getURL()
    {
	if (myUrl != null)
	{
	    return myUrl;
	}
	else return "url non initialisee";
    }

    private Object execute(String urlBorne, String methode, Vector params)
    {
	Object result = null;
	if (urlBorne != null)
	{
	    synchronized(this)
	    {
		try
		{
		    setURL(urlBorne);
		    result = client.execute (methode, params);
		}
		catch (Exception x)
		{
		    System.err.println("Execution de la méthode: " + methode +
				       " impossible sur la borne dont l'url est: " +
				       urlBorne + "  [" + x.toString() + "]");
		}
	    }
	}
	return result;
    }



    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    // certaines methodes rpc sont executées pour chaque borne, ce qui peut
    // prendre un certain temps (de l'ordre de 0,5s par Tini).
    // pour ne pas bloquer le superviseur trop longtemps on peut:
    //    1- creer un Thread pour chaque methode.Mais on n'est pas assuré du synchronisme
    //      (duree d'execution de chaque Thread sur un systeme non temps reel)
    //    2- placer les methodes dans un pipe line qui les executera les unes
    //       à la suite des autres (pile FIFO) dans un seul Thread
    // La 2eme solution est plus souple. C'est elle qui a ete retenue
    // A noter que ces methodes ne doivent rien retourner !!
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    interface RpcMethode
    {
	void doExecute();
    }

    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    class ExecMethodes implements Runnable
    {
	//java.util.List liste = Collections.synchronizedList(new LinkedList());
	LinkedList maListe = new LinkedList();
	ExecMethodes()
	{
	    Thread myThread = new Thread(this);
	    myThread.setDaemon(true);
	    myThread.setPriority(Thread.NORM_PRIORITY);
	    myThread.start();
	}

	public void addRpcMethode(RpcMethode rpcMethode)
	{
	    synchronized(maListe)
	    {
		maListe.addLast(rpcMethode);
		maListe.notify();
	    }
	}

	private final RpcMethode getRpcMethode()
	{
	    RpcMethode result;
	    synchronized(maListe)
	    {
		try
		{
		    result = ((RpcMethode)maListe.removeFirst());
		}
		catch (NoSuchElementException nsee)
		{
		    result = null;
		}
		if (result == null)
		{
		    try
		    {
			maListe.wait();
		    }
		    catch (InterruptedException e) {}
		}
	    }
	    return result;
	}

	public void run()
	{
	    RpcMethode event;
	    while (true)
	    {
		while ((event = getRpcMethode()) != null)
		{
		    event.doExecute();
		}
	    }
	}

    }

    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    //**************************************************************************
    // destiné à une seule borne
    //**************************************************************************

    public boolean initialiseBorne(int numeroBorne)
    {
	boolean ok = false;
	final String methode = "initBorne";
	final int level = 1;// si la borne est initialisee par le superviseur, pas d'enregistrement automatique ( level = 1)

	Vector params = new Vector(4);
	params.addElement(tBornes.getNom(numeroBorne));
	params.addElement(getUrlServer());
	params.addElement(new Integer(numeroBorne));
	params.addElement(new Integer(level));

	String urlBorne = tBornes.getUrl(numeroBorne);
	ok = ((Boolean)execute(urlBorne, methode, params)).booleanValue();
	return ok;
    }


    public void messageGardien(int numeroBorne, String message)
    {
	final String methode = "afficheMessage";
	Vector params = new Vector(1);
	params.addElement(message);
	String url = tBornes.getUrl(numeroBorne);
	boolean r = ((Boolean)execute(url, methode, params)).booleanValue();// r :uniquement pour provoquer exception
    }

    public String codeCAP(int numeroBorne, boolean write, final String code)
    {
	final String methode = "carteAP";
	Vector params = new Vector(2);
	params.addElement(new Boolean(write));
	params.addElement(code);
	String url = tBornes.getUrl(numeroBorne);
	return (String)execute(url, methode, params);
    }

    public void autoriseEntree(int numeroBorne)
    {
	final String methode = "setAutorisation";
	Vector params = new Vector();
	String urlBorne = tBornes.getUrl(numeroBorne);
	execute (urlBorne, methode, params);
    }

//******************************************************************************
//   destiné à toutes les bornes
//******************************************************************************

    public void eclairageAllBornes(final boolean on)
    {
	execMethodes.addRpcMethode(new SetBackLight(-1, true, on));
    }

    public void processAlarme(final boolean on)
    {
	execMethodes.addRpcMethode(new SetAlarme(on));
    }

    public void setParkingPlein(final boolean on)
    {
	execMethodes.addRpcMethode(new SetStateFull(on));
    }


//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    class SetBackLight implements RpcMethode
    {
	final String methode = "eclaire";
	int numeroBorne;
	Vector params = new Vector(2);

	SetBackLight(int numeroBorne, boolean modif, boolean on)
	{
	    this.numeroBorne = numeroBorne;
	    params.add(new Boolean(modif));
	    params.add(new Boolean(on));
	}

	public void doExecute()
	{
	    if ( numeroBorne < 0)
	    {
		Vector allBornes = tBornes.getAllBornes();
		int nombreDeBornes = allBornes.size();
		for (int i = 0; i < nombreDeBornes; i++)
		{
		    Vector v = (Vector)allBornes.elementAt(i);
		    String urlBorne = (String)v.elementAt(2);
		    Object r = execute(urlBorne, methode, params);
		}
	    }
	    else
	    {
		String urlBorne = tBornes.getUrl(numeroBorne);
		Object r = execute(urlBorne, methode, params);
	    }
	}
    }

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    class SetAlarme implements RpcMethode
    {
	final String methode = "setAlarme";
	Vector params = new Vector(1);

	SetAlarme(boolean on)
	{
	    params.add(new Boolean(on));
	}

	public void doExecute()
	{
	    Vector allBornes = tBornes.getAllBornes();
	    int nombreDeBornes = allBornes.size();
	    for (int i = 0; i < nombreDeBornes; i++)
	    {
		Vector v = (Vector)allBornes.elementAt(i);
		String urlBorne = (String)v.elementAt(2);
		Object r = execute (urlBorne, methode, params);
	    }
	}
    }

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    class SetStateFull implements RpcMethode
    {
	final String methode = "parkingPlein";
	//int numeroBorne;
	Vector params = new Vector(1);

	SetStateFull(boolean on)
	{
	    params.add(new Boolean(on));
	}

	public void doExecute()
	{
	    Vector allBornes = tBornes.getAllBornes();
	    int nombreDeBornes = allBornes.size();
	    for (int i = 0; i < nombreDeBornes; i++)
	    {
		Vector v = (Vector)allBornes.elementAt(i);
		String urlBorne = (String)v.elementAt(2);
		Object o = execute (urlBorne, methode, params);
	    }
	}
    }

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    class WatchDog implements RpcMethode
    {
	final String methode = "wd";
	Vector params = new Vector(1);

	WatchDog()
	{
	    params.addElement("test");
	}

	public void doExecute()
	{
	    Vector vDefaut = null;
	    Vector allBornes = tBornes.getAllBornes();
	    int nombreDeBornes = allBornes.size();
	    for (int i = 0; i < nombreDeBornes; i++)
	    {
		Vector v = (Vector)allBornes.elementAt(i);
		int numeroBorne = ((Integer)v.elementAt(1)).intValue();
		String urlBorne = (String)v.elementAt(2);
		if (urlBorne != null)
		{
		    boolean watchdogOK = false;
		    Object o = execute(urlBorne, methode, params);
		    if (o != null)watchdogOK = "test".equals((String)o);
		    if (!watchdogOK)
		    {
			vDefaut = getVDefaut(numeroBorne);
			if (vDefaut == null)
			{
			    ParkingCenter.getInstance().getGardiens().defautBorne(true, numeroBorne, "défaut liaison");
			    defautBornes.addElement(v);
			    //System.out.println("ajout defaut. Total: " + defautBornes.size());
			}
		    }
		    else
		    {
			vDefaut = getVDefaut(numeroBorne);
			if (vDefaut != null)
			{
			    ParkingCenter.getInstance().getGardiens().defautBorne(false, numeroBorne, "plus de defaut liaison");
			    defautBornes.remove(vDefaut);
			    //System.out.println("retrait defaut. Total: " + defautBornes.size());
			}
		    }
		}
	    }
	}
    }


//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//                  TEST LIAISONS BORNES
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    private void startTestLiaisonBornes()
    {

	clockTest = new ClockTest();
	Thread threadClock = new Thread(clockTest);
	threadClock.setName("threadTest");
	threadClock.setPriority(1);
	threadClock.setDaemon(false);
	threadClock.start();
    }

    private void stopTestLiaisonBornes()
    {
	clockTest.stopTest();
	//while( clockTest.myThread.isAlive()) {}
    }

    class ClockTest implements Runnable
    {
	boolean stopTest = false;
	Thread myThread;

	public void stopTest()
	{
	    stopTest = true;
	}

	public void run()
	{
	    Thread myThread = Thread.currentThread();
	    try
	    {
		while (!stopTest)
		{
		    execMethodes.addRpcMethode(new WatchDog());
		    myThread.sleep(15000);
		}
	    }
	    catch (Exception exc)
	    {
		exc.printStackTrace();
	    }
	}
    }

}